package tc.de.krawallmann.persistence;

import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;

/**
 * The class XMLStorage serializes and deserializes objects using *.xml files<br/>
 * These files can also be en-/decrypted
 * 
 * @author KrawallMann
 * @version 1.0
 */
public final class XMLStorage {

	/** The Constant DES_ENCRYPTION. */
	private static final String DES_ENCRYPTION = "DES";

	/** Dis the corrent persistence operation fail? For internal use only!. */
	private static boolean failed = false;

	/**
	 * Deserializes an object from the given file.
	 * 
	 * @param <T>
	 *            the generic type
	 * @param typeReference
	 *            Object of the type of the deserialized object.<br/>
	 *            May be null, but do not simply provide null as an agrument!<br/>
	 *            This is used to find out the generic type of the object!
	 * @param file
	 *            the file in which the object is stored
	 * @return the deserialized object of the type T_ARRAY
	 * @throws XMLPersistenceException
	 *             if the file is corrupted
	 * @throws FileNotFoundException
	 *             if the file was not found
	 */
	@SuppressWarnings("unchecked")
	public static <T> T deserialize(final T typeReference, final String file)
			throws XMLPersistenceException, FileNotFoundException {
		final InputStream inStream = new FileInputStream(file);
		final XMLDecoder decoder = new XMLDecoder(inStream);
		Object readObject = null;
		decoder.setExceptionListener(new ExceptionListener() {
			@Override
			public void exceptionThrown(final Exception exception) {
				XMLStorage.failed = true;
			}
		});
		readObject = decoder.readObject();
		decoder.close();
		if ((readObject == null) || XMLStorage.failed) {
			XMLStorage.failed = false;
			throw new XMLPersistenceException("The object read from " + file + " is null!");
		}
		return (T) readObject;
	}

	/**
	 * Deserializes an object from the given file, using DES decryption.
	 * 
	 * @param <T>
	 *            the generic type
	 * @param typeReference
	 *            Object of the type of the deserialized object.<br/>
	 *            May be null, but do not simply provide null as an agrument!<br/>
	 *            This is used to find out the generic type of the object!
	 * @param file
	 *            the file in which the object is stored
	 * @param key
	 *            the decryption key is made of 8 digits
	 * @return the deserialized object of the type T_ARRAY
	 * @throws XMLPersistenceException
	 *             if the file is corrupted
	 * @throws FileNotFoundException
	 *             if the file was not found
	 */
	@SuppressWarnings("unchecked")
	public static <T> T deserialize(final T typeReference, final String file, final String key)
			throws XMLPersistenceException, FileNotFoundException {
		InputStream inStream = new FileInputStream(file);
		if ((key != null) && !key.isEmpty()) {
			try {
				final Cipher cipher = Cipher.getInstance(XMLStorage.DES_ENCRYPTION);
				final Key encryptionKey =
						new SecretKeySpec(key.getBytes(), XMLStorage.DES_ENCRYPTION);
				cipher.init(Cipher.DECRYPT_MODE, encryptionKey);
				inStream = new CipherInputStream(inStream, cipher);
			} catch (final GeneralSecurityException exception) {
				throw new XMLPersistenceException(exception);
			}
		}
		final XMLDecoder decoder = new XMLDecoder(inStream);
		Object readObject = null;
		decoder.setExceptionListener(new ExceptionListener() {
			@Override
			public void exceptionThrown(final Exception exception) {
				XMLStorage.failed = true;
			}
		});
		readObject = decoder.readObject();
		decoder.close();
		if ((readObject == null) || XMLStorage.failed) {
			XMLStorage.failed = false;
			throw new XMLPersistenceException("The object read from " + file + " is null!");
		}
		return (T) readObject;
	}

	/**
	 * Store.
	 * 
	 * @param <T>
	 *            the generic type
	 * @param toSave
	 *            the object to be saved
	 * @param file
	 *            the file in which the object is stored
	 * @throws XMLPersistenceException
	 *             if a problem occurred
	 * @throws FileNotFoundException
	 *             if the file was not found
	 */
	public static <T> void store(final T toSave, final String file) throws XMLPersistenceException,
			FileNotFoundException {
		final OutputStream outStream = new FileOutputStream(file);
		final XMLEncoder encoder = new XMLEncoder(outStream);
		encoder.writeObject(toSave);
		encoder.close();
	}

	/**
	 * Store.
	 * 
	 * @param <T>
	 *            the generic type
	 * @param toSave
	 *            the object to be saved
	 * @param file
	 *            the file in which the object is stored
	 * @param key
	 *            the encryption key is made of 8 digits
	 * @throws XMLPersistenceException
	 *             if a problem occurred
	 * @throws FileNotFoundException
	 *             if the file was not found
	 */
	public static <T> void store(final T toSave, final String file, final String key)
			throws XMLPersistenceException, FileNotFoundException {
		OutputStream outStream = new FileOutputStream(file);
		if ((key != null) && !key.isEmpty()) {
			try {
				final Cipher cipher = Cipher.getInstance(XMLStorage.DES_ENCRYPTION);
				final Key encryptionKey =
						new SecretKeySpec("14454521".getBytes(), XMLStorage.DES_ENCRYPTION);
				cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
				outStream = new CipherOutputStream(outStream, cipher);
			} catch (final GeneralSecurityException exception) {
				throw new XMLPersistenceException(exception);
			}
		}
		final XMLEncoder encoder = new XMLEncoder(outStream);
		encoder.writeObject(toSave);
		encoder.close();
	}

	/**
	 * Instantiates a new xML storage.
	 */
	private XMLStorage() {
		super();
		// Singleton constructor!
	}
}
